# -*- coding: utf-8 -*-
from __future__ import division
from __future__ import print_function

import abc
import six
import numpy as np
import pandas as pd

from .data import D
from .cache import H

@six.add_metaclass(abc.ABCMeta)
class Feature(object):
    """Feature base class"""
    
    def __str__(self):
        return type(self).__name__.lower()

    def __repr__(self):
        return str(self)

    def __gt__(self, other):
        from .ops import Gt
        return Gt(self, other)

    def __ge__(self, other):
        from .ops import Ge
        return Ge(self, other)

    def __lt__(self, other):
        from .ops import Lt
        return Lt(self, other)

    def __le__(self, other):
        from .ops import Le
        return Le(self, other)

    def __eq__(self, other):
        from .ops import Eq
        return Eq(self, other)

    def __ne__(self, other):
        from .ops import Ne
        return Ne(self, other)

    def __add__(self, other):
        from .ops import Add
        return Add(self, other)

    def __radd__(self, other):
        from .ops import Add
        return Add(other, self)

    def __sub__(self, other):
        from .ops import Sub
        return Sub(self, other)

    def __rsub__(self, other):
        from .ops import Sub
        return Sub(other, self)

    def __mul__(self, other):
        from .ops import Mul
        return Mul(self, other)

    def __rmul__(self, other):
        from .ops import Mul
        return Mul(self, other)

    def __truediv__(self, other):
        from .ops import Div
        return Div(self, other)

    def __rtruediv__(self, other):
        from .ops import Div
        return Div(other, self)

    def __and__(self, other):
        from .ops import And
        return And(self, other)

    def __rand__(self, other):
        from .ops import And
        return And(other, self)

    def __or__(self, other):
        from .ops import Or
        return Or(self, other)

    def __ror__(self, other):
        from .ops import Or
        return Or(other, self)
    
    def load(self, instrument, start_time, end_time, freq):
        """load  feature

        Parameters
        ----------
        instrument : str
            instrument code
        start_time : str
            feature start time
        end_time : str
            feature end time
        freq : str
            feature frequency

        Returns
        ----------
        pd.Series
            feature series
        """
        # cache
        args = str(self), instrument, start_time, end_time, freq
        if args in H:
            return H[args]
        if start_time is None or end_time is None or start_time > end_time:
            raise ValueError('Invalid time range: {} {}'.format(start_time, end_time))
        start_time = pd.Timestamp(start_time)
        end_time = pd.Timestamp(end_time)
        series = self._load_internal(instrument, start_time, end_time, freq)
        series.name = str(self)
        H[args] = series
        return series

    @abc.abstractmethod
    def _load_internal(self, instrument, start_time, end_time, freq):
        pass
    
    
class StaticFeature(Feature):
    """Static Feature 
    
    This kind of feature will load data from provider
    """
    def _load_internal(self, instrument, start_time, end_time, freq):
        # load
        return D._load_feature(instrument, str(self), start_time, end_time, freq)


@six.add_metaclass(abc.ABCMeta)    
class OpsFeature(Feature):
    """Operator Feature
    
    This kind of feature will use operator for feature
    construction on the fly.
    """
    pass
